// Copyright 2017 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT

#ifndef ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_EXCP_PORT_H_
#define ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_EXCP_PORT_H_

#include <stdint.h>
#include <zircon/syscalls/exception.h>
#include <zircon/syscalls/port.h>
#include <zircon/types.h>

#include <arch/exception.h>
#include <fbl/canary.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/mutex.h>
#include <fbl/ref_counted.h>
#include <fbl/ref_ptr.h>
#include <kernel/lockdep.h>
#include <kernel/mutex.h>
#include <object/dispatcher.h>

class ThreadDispatcher;
class ProcessDispatcher;
class PortDispatcher;

// Represents the binding of an exception port to a specific target
// (job/process/thread). Multiple ExceptionPorts may exist for a
// single underlying PortDispatcher.
class ExceptionPort : public fbl::DoublyLinkedListable<fbl::RefPtr<ExceptionPort>>,
                      public fbl::RefCounted<ExceptionPort> {
 public:
  enum class Type { NONE, JOB_DEBUGGER, DEBUGGER, THREAD, PROCESS, JOB };

  static zx_status_t Create(Type type, fbl::RefPtr<PortDispatcher> port, uint64_t port_key,
                            fbl::RefPtr<ExceptionPort>* eport);
  ~ExceptionPort();

  Type type() const { return type_; }

  zx_status_t SendPacket(ThreadDispatcher* thread, uint32_t type);

  void OnThreadStartForDebugger(ThreadDispatcher* thread, const arch_exception_context_t* context);
  void OnThreadExitForDebugger(ThreadDispatcher* thread);
  void OnProcessStartForDebugger(ThreadDispatcher* thread, const arch_exception_context_t* context);

  // Records the target that the ExceptionPort is bound to, so it can
  // unbind when the underlying PortDispatcher dies.
  void SetTarget(const fbl::RefPtr<JobDispatcher>& target);
  void SetTarget(const fbl::RefPtr<ProcessDispatcher>& target);
  void SetTarget(const fbl::RefPtr<ThreadDispatcher>& target);

  // Drops the ExceptionPort's references to its target and PortDispatcher.
  // Called by the target when the port is explicitly unbound.
  void OnTargetUnbind();

  // Validates that this eport is associated with the given instance.
  bool PortMatches(const PortDispatcher* port, bool allow_null);
  bool PortMatchesLocked(const PortDispatcher* port, bool allow_null) TA_REQ(lock_);

  static void BuildArchReport(zx_exception_report_t* report, uint32_t type,
                              const arch_exception_context_t* arch_context);

 private:
  friend class PortDispatcher;

  ExceptionPort(Type type, fbl::RefPtr<PortDispatcher> port, uint64_t port_key);

  ExceptionPort(const ExceptionPort&) = delete;
  ExceptionPort& operator=(const ExceptionPort&) = delete;

  zx_status_t SendPacketWorker(uint32_t type, zx_koid_t pid, zx_koid_t tid);

  // Unbinds from the target if bound, and drops the ref to |port_|.
  // Called by |port_| when it reaches zero handles.
  void OnPortZeroHandles();

  // Returns true if the ExceptionPort is currently bound to a target.
  bool IsBoundLocked() const TA_REQ(lock_) { return target_ != nullptr; }

  static void BuildReport(zx_exception_report_t* report, uint32_t type);

  fbl::Canary<fbl::magic("EXCP")> canary_;

  // These aren't locked as once the exception port is created these are
  // immutable (the port itself has its own locking though).
  const Type type_;
  const uint64_t port_key_;

  // The underlying port. If null, the ExceptionPort has been unbound.
  fbl::RefPtr<PortDispatcher> port_ TA_GUARDED(lock_);

  // The target of the exception port.
  // The system exception port doesn't have a Dispatcher, hence the bool.
  fbl::RefPtr<Dispatcher> target_ TA_GUARDED(lock_);

  DECLARE_MUTEX(ExceptionPort) lock_;

  // NOTE: The DoublyLinkedListNodeState is guarded by |port_|'s lock,
  // and should only be touched using port_->LinkExceptionPort()
  // or port_->UnlinkExceptionPort(). This goes for ::InContainer(), too.
};

#endif  // ZIRCON_KERNEL_OBJECT_INCLUDE_OBJECT_EXCP_PORT_H_
